﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Security.Permissions;
using System.Diagnostics;
using System.Xml;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Automation.Provider;
using System.Windows.Automation;
using AutomationFramework;

namespace TreeControl
{
    [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
    public class MeasurementControl : UserControl
    {
        #region Constants
        private const int AUTO_POP_DELAY = 5000;
        private const int INITIAL_DELAY = 200;
        private const int RESHOW_DELAY = 100;
        #endregion

        #region Fields
        private MeasurementItem myHeaderItem;
        public IDictionary<string, MeasurementItem> myMeasurementsCollection = new Dictionary<string, MeasurementItem>();
        private ToolTip myToolTip;

        private string myId;
        private bool myIsExpanded;
        private bool myExpandActive;

        private StepState myHeaderState = StepState.NonActive;
        private InteractionState myInteractionState = InteractionState.Enabled;

        private Size myDefaultSize;
        private Size myFlexibleSize;
        private Point myInitialPoint;
        private Point myFinalPoint;
        #endregion

        #region Events
        public event EventHandler<HeightChangedEventArgs> HeightChanged;

        public event EventHandler<NodeClickedEventArgs> NodeCollapsed;

        public event EventHandler<NodeClickedEventArgs> NodeDoubleClicked;

        public event EventHandler<NodeClickedEventArgs> NodeExpanded;

        public event EventHandler<NodeClickedEventArgs> NodeSelected;
        #endregion

        #region Constructors
        public MeasurementControl(string id)
        {
            if (string.IsNullOrEmpty(id))
            {
                throw new ArgumentNullException("id", "id cannot be null or empty.");
            }
            #region Set Style
            SetStyle(ControlStyles.Selectable, true);
            SetStyle(ControlStyles.ResizeRedraw, true);
            // Set the value of the double-buffering style bits to true.
            this.SetStyle(ControlStyles.DoubleBuffer |
               ControlStyles.UserPaint |
               ControlStyles.AllPaintingInWmPaint,
               true);
            this.UpdateStyles();
            #endregion
            this.Id = id;
            this.BackColor = Configurations.MenuItemBackColor;
            this.Size = myDefaultSize = myFlexibleSize = new Size();

            this.myToolTip = new ToolTip();
            SetToolTipDelays();
            this.myToolTip.ShowAlways = true;
        }
        #endregion

        #region Properties
        public string Id
        {
            get
            {
                return myId;
            }
            set
            {
                myId = value;
                this.Name = myId;
            }
        }

        public bool IsExpanded
        {
            get
            {
                return myIsExpanded;
            }
            set
            {
                if (myIsExpanded != value && myMeasurementsCollection.Count > 1)
                {
                    myIsExpanded = value;
                    myHeaderItem.IsExpanded = myIsExpanded;

                    this.Invalidate(myHeaderItem.ExpandIconBounds);

                    int delta = myFlexibleSize.Height - myDefaultSize.Height;
                    if (myIsExpanded)
                    {
                        foreach (MeasurementItem item in myMeasurementsCollection.Values)
                        {
                            item.CalculateRectangles();
                        }
                        this.Size = myFlexibleSize;
                    }
                    else
                    {
                        this.Size = myDefaultSize;
                        delta = -delta;
                    }

                    if (delta != 0)
                    {
                        if (HeightChanged != null)
                        {
                            HeightChangedEventArgs e = new HeightChangedEventArgs(delta);
                            HeightChanged(this, e);
                        }

                        EventHandler<NodeClickedEventArgs> temp = myIsExpanded ? NodeExpanded : NodeCollapsed;
                        if (temp != null)
                        {
                            NodeClickedEventArgs e = new NodeClickedEventArgs(this.Id);
                            temp(this, e);
                        }
                    }
                }
            }
        }

        public bool IsSelected
        {
            get
            {
                return myHeaderItem.IsSelected;

            }
            set
            {
                myHeaderItem.IsSelected = value;
            }
        }

        public StepState HeaderState
        {
            get
            {
                return myHeaderState;
            }
            set
            {
                myHeaderState = value;
                SetMeasurementsState(myHeaderState);
            }
        }

        public bool IsEnabled
        {
            get
            {
                if (HeaderState == StepState.Disabled)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
            set
            {
                if (!value)
                {
                    HeaderState = StepState.Disabled; 
                }
                else
                {
                    HeaderState = StepState.Enabled;
                }
            }
        }
        #endregion

        #region Internal Methods
        public void AddMeasurementItem(string id, string text, MeasurementType type, Behavior behavior)
        {
            int locationY = myMeasurementsCollection.Count * Configurations.ItemHeight;
            if (this.Width <= 0)
            {
                this.Width = Configurations.ItemWidth;
            }
            MeasurementItem item = new MeasurementItem(id, text, type, behavior, this.Width, locationY);

            item.SetParent(this);

            myMeasurementsCollection.Add(item.IdFromXmlMatadata, item);

            if (myHeaderItem == null)
            {
                myHeaderItem = item;
                // TODO: Read this info thru license or from somewhere...
                myHeaderItem.IsAutoIconEnabled = true;
                myHeaderItem.Indent = 0;
                myHeaderItem.CalculateRectangles();

                myHeaderItem.Id = this.Id;
                this.Text = myHeaderItem.Label;
                myToolTip.SetToolTip(this, myHeaderItem.Label);
                this.Size = myDefaultSize = myFlexibleSize = new Size(this.Width, Configurations.ItemHeight);
            }
        }

        public void FinalizeCreation()
        {
            int height = Configurations.ItemHeight;

            if (myMeasurementsCollection.Count > 0)
            {
                height = height * myMeasurementsCollection.Count;
            }
            myFlexibleSize = new Size(myFlexibleSize.Width, height);

            // Calculate points for expand icon line.
            CalculatePoints();
        }

        public MeasurementItem ValueFromPoint(Point clientPoint)
        {
            MeasurementItem item = GetClickedItem(clientPoint);
            return item;
        }

        #endregion

        #region Overridden Methods
        protected override void OnSizeChanged(EventArgs e)
        {
            foreach (MeasurementItem item in myMeasurementsCollection.Values)
            {
                item.Width = this.Width;
                item.CalculateRectangles();
            }
            base.OnSizeChanged(e);
        }

        protected override void OnParentChanged(EventArgs e)
        {
            Control parent = this.Parent as Control;
            if (parent != null)
            {
                this.Parent = parent;
            }
            base.OnParentChanged(e);
        }

        protected override void OnBackColorChanged(EventArgs e)
        {
            foreach (MeasurementItem item in myMeasurementsCollection.Values)
            {
                item.BackColor = this.BackColor;
            }
            base.OnBackColorChanged(e);
        }

        protected override void OnLeave(EventArgs e)
        {
            foreach (MeasurementItem item in myMeasurementsCollection.Values)
            {
                item.IsSelected = false;
                this.Invalidate(item.LabelBounds);
            }
            base.OnLeave(e);
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            myToolTip.Hide(this);
            base.OnMouseLeave(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (e.Button != MouseButtons.None)
            {
                myToolTip.Hide(this);
                return;
            }

            Point hitLocation = e.Location;

            // Get Clicked Item
            MeasurementItem clickedItem = GetClickedItem(hitLocation);

            if (clickedItem == null)
            {
                myToolTip.Hide(this);
                return;
            }

            if (string.Compare(myToolTip.GetToolTip(this), clickedItem.ToolTip) == 0)
            {
                return;
            }
            string toolTipText = clickedItem.ToolTip;

            if (!string.IsNullOrEmpty(toolTipText))
            {
                myToolTip.Hide(this);
                myToolTip.SetToolTip(this, toolTipText);
                myToolTip.Hide(this);
            }
        }

        protected override void OnClick(EventArgs e)
        {
            MouseEventArgs args = e as MouseEventArgs;
            if (args == null || args.Button != MouseButtons.Left)
            {
                return;
            }
            ClickHandler(args);
            base.OnClick(e);
        }

        protected override void OnDoubleClick(EventArgs e)
        {
            MouseEventArgs args = e as MouseEventArgs;
            if (args == null || args.Button != MouseButtons.Left)
            {
                return;
            }
            DoubleClickHandler(args);
            base.OnDoubleClick(e);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaint(PaintEventArgs e)
        {
            if (IsExpanded)
            {
                foreach (MeasurementItem item in myMeasurementsCollection.Values)
                {
                    item.Draw(e.Graphics);
                }

                e.Graphics.DrawLine(Configurations.VerticalLinePen, myInitialPoint, myFinalPoint);

            }
            else
            {
                if (myHeaderItem != null)
                {
                    myHeaderItem.Draw(e.Graphics);
                }
            }

            base.OnPaint(e);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        public override bool PreProcessMessage(ref Message msg)
        {
            // Required since the HeaderedItem reads the key stroke and moves 
            // the focus to next/previous HeaderedItem. Disabling that feature.
            const int WM_SYSKEYDOWN = 0x104;
            const int WM_KEYDOWN = 0x100;
            if ((msg.Msg == WM_KEYDOWN) || (msg.Msg == WM_SYSKEYDOWN))
            {
                Trace.WriteLine("HeaderedItem-PreProcessMessage-OnKeyPress");
                return false;
            }
            return base.PreProcessMessage(ref msg);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (myToolTip != null)
                {
                    myToolTip.RemoveAll();
                    myToolTip.Dispose();
                    myToolTip = null;
                }
            }
            base.Dispose(disposing);
        }
        #endregion

        #region Private Methods

        private void ClickHandler(MouseEventArgs args)
        {
            myToolTip.Hide(this);

            Point hitLocation = args.Location;

            // Get Clicked Item
            MeasurementItem clickedItem = GetClickedItem(hitLocation);

            if (clickedItem != null)
            {
                HitArea ht = clickedItem.GetHitArea(hitLocation);

                if (ht == HitArea.ExpandIcon)
                {
                    IsExpanded = !IsExpanded;
                }
                else
                {
                    bool raiseNodeClickedEvent = false;
                    #region Get if node is selected or expanded.
                    foreach (MeasurementItem item in myMeasurementsCollection.Values)
                    {
                        if (item != clickedItem)
                        {
                            item.IsSelected = false;
                        }
                        else
                        {
                            if (myInteractionState == InteractionState.Disabled && item.MeasurementType == MeasurementType.MeasurementGroup)
                            {
                                IsExpanded = !IsExpanded;
                            }
                            else
                            {
                                item.IsSelected = true;
                                raiseNodeClickedEvent = true;
                            }
                        }
                        this.Invalidate(item.LabelBounds);
                    }
                    #endregion
                    if (raiseNodeClickedEvent)
                    {
                        if (myInteractionState == InteractionState.Enabled)
                        {
                            if (NodeSelected != null)
                            {
                                if ((myHeaderState != StepState.Active) ||
                                    (myHeaderState == StepState.Active && string.Compare(myHeaderItem.Id, clickedItem.Id) == 0))
                                {
                                    NodeClickedEventArgs eArgs = new NodeClickedEventArgs(clickedItem.Id);
                                    NodeSelected(this, eArgs);
                                }
                            }
                        }
                    }
                }
            }
        }

        private void DoubleClickHandler(MouseEventArgs args)
        {
            myToolTip.Hide(this);

            Point hitLocation = args.Location;

            // Get Clicked Item
            MeasurementItem clickedItem = GetClickedItem(hitLocation);

            if (clickedItem != null)
            {
                if (NodeDoubleClicked != null)
                {
                    NodeClickedEventArgs ncArgs = new NodeClickedEventArgs(clickedItem.Id);
                    NodeDoubleClicked(this, ncArgs);
                }
            }
        }

        private MeasurementItem GetClickedItem(Point location)
        {
            MeasurementItem clickedItem = null;

            foreach (MeasurementItem item in myMeasurementsCollection.Values)
            {
                if (item.BoundingRectangle.Contains(location))
                {
                    clickedItem = item;
                    break;
                }
            }
            return clickedItem;
        }

        /// <summary>
        /// Set the delays for the ToolTip.
        /// </summary>
        private void SetToolTipDelays()
        {
            // ToolTip remains visible for AUTO_POP_DELAY milli second when the pointer is stationary on the control.
            myToolTip.AutoPopDelay = AUTO_POP_DELAY;
            // pointer must remain stationary on a control for INITIAL_DELAY milli second before the ToolTip window is displayed.
            myToolTip.InitialDelay = INITIAL_DELAY;
            // After RESHOW_DELAY milli second subsequent ToolTip windows to appear.
            myToolTip.ReshowDelay = RESHOW_DELAY;
        }

        private void CalculatePoints()
        {
            Rectangle expandIconBounds = myHeaderItem.ExpandIconBounds;
            int iconHeight = Configurations.ExpandedIcon.Height;
            int centreLocationY = (Configurations.IconDefaultSize.Height - iconHeight) / 2;

            int linePosition = expandIconBounds.Left + (expandIconBounds.Width / 2);
            int height = expandIconBounds.Height - (iconHeight + centreLocationY);

            myInitialPoint = new Point(linePosition, expandIconBounds.Bottom - height);
            myFinalPoint = new Point(linePosition, myFlexibleSize.Height);
        }

        private void SetMeasurementsState(StepState myHeaderState)
        {
            State childState = myHeaderItem.ItemState;
            bool recursive = false;
            switch (myHeaderState)
            {
                case StepState.NonActive:
                    childState = State.Unchecked;
                    if (!myExpandActive)
                    {
                        this.IsExpanded = false;
                    }
                    break;
                case StepState.Failed:
                    childState = State.Failed;
                    if (!myExpandActive)
                    {
                        this.IsExpanded = false;
                    }
                    break;
                case StepState.Completed:
                    childState = State.Checked;
                    if (!myExpandActive)
                    {
                        this.IsExpanded = false;
                    }
                    break;
                case StepState.Active:
                    childState = State.Active;
                    if (!myExpandActive)
                    {
                        this.IsExpanded = true;
                    }
                    break;
                case StepState.Collapsed:
                    this.IsExpanded = false;
                    break;
                case StepState.Expanded:
                    this.IsExpanded = true;
                    break;
                case StepState.Disabled:
                    myInteractionState = InteractionState.Disabled;
                    recursive = true;
                    break;
                case StepState.Enabled:
                    myInteractionState = InteractionState.Enabled;
                    recursive = true;
                    break;
            }
            myHeaderItem.ItemState = childState;
            bool enabled = (myInteractionState == InteractionState.Enabled) ? true : false;

            if (recursive)
            {
                foreach (MeasurementItem childItem in myMeasurementsCollection.Values)
                {
                    childItem.IsEnabled = enabled;
                    this.Invalidate(childItem.LabelBounds);
                    this.Invalidate(childItem.AutoIconBounds);
                }
            }
            else
            {
                myHeaderItem.IsEnabled = enabled;
                this.Invalidate(myHeaderItem.StateIconBounds);
                this.Invalidate(myHeaderItem.LabelBounds);
                this.Invalidate(myHeaderItem.AutoIconBounds);
            }
        }
        #endregion

        #region Automation Framework Related Methods
        #region Temp Methods
        public MeasurementItem[] Items
        {
            get
            {
                MeasurementItem[] array = new MeasurementItem[myMeasurementsCollection.Values.Count];
                myMeasurementsCollection.Values.CopyTo(array, 0);
                return array;
            }
        }
        #endregion

        protected override void WndProc(ref Message m)
        {
            // Handle WM_GETOBJECT
            if (m.Msg == 0x3D /* WM_GETOBJECT */)
            {
                m.Result = AutomationInteropProvider.ReturnRawElementProvider(
                    m.HWnd, m.WParam, m.LParam, this.Provider);
            }
            else
            {
                base.WndProc(ref m);
            }
        }

        private ControlProvider provider;
        private IRawElementProviderSimple Provider
        {
            get
            {
                if (this.provider == null)
                {
                    this.provider = new ControlProvider(this, ConfigFiles.MeasurementControlApiMapping);
                }
                return this.provider;
            }
        }
        #endregion
    }
}
